#include "C3DS_Loader.h"
#include <iostream>
#include <cstdlib>
#include <io.h>
using namespace std;

#define MAIN_CHUNK 0x4D4D
  #define MODEL_EDITOR_CHUNK 0x3D3D
     #define OBJECT_BLOCK 0x4000
        #define TRIANGULAR_MESH 0x4100
           #define VERTICES_LIST 0x4110
           #define FACES_DESCRIPTION 0x4120
              #define FACES_MATERIAL 0x4130           // not in use
           #define TEXTURE_COORDINATES 0x4140    
              #define SMOOTHING_GROUP_LIST 0x4150     // not in use
           #define LOCAL_COORDINATES_SYSTEM 0x4160    // not in use
        #define LIGHT 0x4600                          // not in use
           #define SPOTLIGHT 0x4610                   // not in use
        #define CAMERA 0x4700                         // not in use
     #define MATERIAL_BLOCK 0xAFFF
        #define MATERIAL_NAME 0xA000
        #define AMBIENT_COLOR 0xA010                  // not in use
        #define DIFFUSE_COLOR 0xA020
        #define SPECULAR_COLOR 0xA030                 // not in use
        #define TEXTURE_MAP 1 0xA200                  // not in use
        #define BUMP_MAP 0xA230                       // not in use
        #define REFLECTION_MAP 0xA220                 // not in use

// Used in keeping a global texture counter
extern unsigned int texture_counter;

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
//// C3DModel Class - base class for 3ds models
////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

C3DModel::C3DModel() 
{
   position.x=0; position.y=0; position.z=0; x_ang = 90; y_ang = 0; z_ang=0;
   TGA_texture.ID=0;
};

C3DModel::C3DModel(const C3DModel & model)
{
	num_obj=model.num_obj;
	mesh_objs = new MESH_Object[num_obj];
	for(int i=0; i<num_obj; i++)
	{
		strcpy(mesh_objs[i].name,model.mesh_objs[i].name);
		mesh_objs[i].num_vertices = model.mesh_objs[i].num_vertices;
		mesh_objs[i].num_polygons = model.mesh_objs[i].num_polygons;
		mesh_objs[i].num_texCoord = model.mesh_objs[i].num_texCoord;
		mesh_objs[i].vertex = new CVector3[mesh_objs[i].num_vertices];
		for(int h=0; h<mesh_objs[i].num_vertices; h++)
			mesh_objs[i].vertex[h] = model.mesh_objs[i].vertex[h];
		mesh_objs[i].polygons = new POLY_type[mesh_objs[i].num_polygons];
		for(int h=0; h<mesh_objs[i].num_polygons; h++)
			mesh_objs[i].polygons[h] = model.mesh_objs[i].polygons[h];
		mesh_objs[i].texCoord = new UVCoord2[mesh_objs[i].num_texCoord];
		for(int h=0; h<mesh_objs[i].num_texCoord; h++)
			mesh_objs[i].texCoord[h] = model.mesh_objs[i].texCoord[h];
		for(int h=0; h<3; h++)
			mesh_objs[i].diffuse_color[h]=model.mesh_objs[i].diffuse_color[h];
	}
	TGA_texture = model.TGA_texture;
	list = model.list;
	position = model.position;
	x_ang=model.x_ang;
	y_ang=model.y_ang;
	z_ang=model.z_ang;
	boundingBox=model.boundingBox;
	boundingSphere=model.boundingSphere;
	texture_ID = model.texture_ID;
};

// Loads the Mesh + some material properties
bool C3DModel::loadMesh(char * file) 
{
   pFile = fopen(file,"rb");
   if(pFile==NULL) { cout << "File " << &file << "does not exist.\n"; return 0;}
   
   unsigned int obj_count=0;
   unsigned short chunk_id;
   unsigned int chunk_length;
   unsigned char char_;
   unsigned short amt;
   unsigned short face_flags;
   unsigned short color_id;
   int i,j;

   // Determines how many objects the mesh has
   while(ftell(pFile) < filelength(fileno(pFile)))
   {
      fread(&chunk_id, 2, 1, pFile);
      fread(&chunk_length, 4, 1, pFile);      
      switch(chunk_id)
      {
         case 0x4d4d:
            break;
         case 0x3d3d:
            break;
            
         case 0x4000:
            obj_count++;
            fseek(pFile, chunk_length-6, SEEK_CUR);
            break;

         case 0x4100:
			   break;
         
         case 0x4110:
            fseek(pFile, chunk_length-6, SEEK_CUR);
            break;
         
         case 0x4120:
              fseek(pFile, chunk_length-6, SEEK_CUR);
            break;
         
         case 0x4140:
              fseek(pFile, chunk_length-6, SEEK_CUR);
            break;

         default:
            fseek(pFile, chunk_length-6, SEEK_CUR);
      }
   }
   cout << "Number of Objects: " << obj_count << endl;
   num_obj=obj_count;   
      
   // Allocates memory for the number of objects in the mesh
   mesh_objs = new MESH_Object[obj_count];
   
   // Gets material data
   rewind(pFile);
   j=-1;
   while(ftell(pFile) < filelength(fileno(pFile)))
   {
      fread(&chunk_id, 2, 1, pFile);
      fread(&chunk_length, 4, 1, pFile); 
      switch(chunk_id)
      { 
         case MAIN_CHUNK:
            break;
         
         case MODEL_EDITOR_CHUNK:
            break;
            
         case 0x4000:
            fseek(pFile, chunk_length-6, SEEK_CUR);
            break;
            
         case MATERIAL_BLOCK:
            break;
      
         case MATERIAL_NAME:
            j++;
            fseek(pFile, chunk_length-6, SEEK_CUR);
            break;
         
         case AMBIENT_COLOR:
            fseek(pFile, chunk_length-6, SEEK_CUR);
            break;
      
         case DIFFUSE_COLOR:
            
            fread(&color_id, 2, 1, pFile);
             switch(color_id)
            { 
               case 0x0010:
               cout << "not implemented 1" << endl;
               fseek(pFile, 3, SEEK_CUR);
               break;

               // 24 bit color
               case 0x0011:
               unsigned char temp;
               fseek(pFile, 3, SEEK_CUR);
               fread(&temp,1, 1, pFile);
               mesh_objs[j].diffuse_color[0]=temp/255.0;
               fread(&temp,1, 1, pFile);
               mesh_objs[j].diffuse_color[1]=temp/255.0;
               fread(&temp,1, 1, pFile);
               mesh_objs[j].diffuse_color[2]=temp/255.0;

               fseek(pFile, 6 , SEEK_CUR);
               break;
            
            case 0x0012:
               cout << "not implemented 2" << endl;
               break;
            
            case 0x0013:
               cout << "not implemented 3" << endl;
               break;
            }
            break;
            // The default skips to the next chunk
            default:
               fseek(pFile, chunk_length-6, SEEK_CUR);
         }
   }
   
   // Gets the vertices and texture coordinates
   rewind(pFile);
   j=-1;   
   while(ftell(pFile) < filelength(fileno(pFile)))
   {
      fread(&chunk_id, 2, 1, pFile);
      fread(&chunk_length, 4, 1, pFile);      
      switch(chunk_id)
      {
         case MAIN_CHUNK:
            break;
         
         case MODEL_EDITOR_CHUNK:
            break;
         
         case OBJECT_BLOCK:
            cout << endl;
            j++;
            i=0;
            do {
               fread(&char_, 1, 1, pFile);
               mesh_objs[j].name[i] = char_;
               cout << char_;
               i++;
            }while(char_!='\0' && i<29);
               cout <<  "          Object Num" << j+1;
               cout << endl;
            break;
         
         case TRIANGULAR_MESH:
			   break;
         
         case VERTICES_LIST:
            fread (&amt, sizeof(unsigned short), 1, pFile);
            mesh_objs[j].num_vertices=amt;
            mesh_objs[j].vertex=new CVector3[mesh_objs[j].num_vertices];
            for(i=0; i<amt; i++)
            {
               fread(&mesh_objs[j].vertex[i].x, sizeof(float), 1, pFile);
               fread(&mesh_objs[j].vertex[i].y, sizeof(float), 1, pFile);
               fread(&mesh_objs[j].vertex[i].z, sizeof(float), 1, pFile);
            }
            //cout << "Number of vertices: " << mesh_objs[j].num_vertices << endl;
            break;
         
         case FACES_DESCRIPTION:
            fread(&amt, sizeof(unsigned short), 1, pFile);
            mesh_objs[j].num_polygons=amt;
            mesh_objs[j].polygons = new POLY_type[mesh_objs[j].num_polygons];
            for(i=0; i<amt; i++)
            {
               fread(&mesh_objs[j].polygons[i].a, sizeof(unsigned short), 1, pFile);
               fread(&mesh_objs[j].polygons[i].b, sizeof(unsigned short), 1, pFile);
               fread(&mesh_objs[j].polygons[i].c, sizeof(unsigned short), 1, pFile);
               fread(&face_flags, sizeof(unsigned short), 1, pFile);
            }
            cout << "Number of polygons: " << mesh_objs[j].num_polygons << endl;
            break;
         
         case TEXTURE_COORDINATES:
            fread(&amt, sizeof(unsigned short), 1, pFile);
			mesh_objs[j].num_texCoord=amt;
            mesh_objs[j].texCoord=new UVCoord2[mesh_objs[j].num_texCoord];
			cout << "Number of tex: " << amt << endl;
            for(i=0; i<amt; i++)
            {
               fread(&mesh_objs[j].texCoord[i].u, sizeof(float), 1, pFile);
               fread(&mesh_objs[j].texCoord[i].v, sizeof(float), 1, pFile);
            }
            break;

         // DEFAULT: Go to the next chunk
         default:
            fseek(pFile, chunk_length-6, SEEK_CUR);
			break;
      }
   }
   
   fclose(pFile);
   cout << "Mesh " << file << " loaded.\n";
   return 1; };

bool C3DModel::loadTexture(char * file) {
   texture_file = file;
   if(!TGA_texture.LoadTGA(file)) return 0;
   TGA_texture.ID=texture_counter;
   texture_counter++;
   glGenTextures(1,&TGA_texture.ID);
   
   glBindTexture(GL_TEXTURE_2D, TGA_texture.ID);
   // texture params
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // If the u,v coordinates overflow the range 0,1 the image is repeated
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // The magnification function ("linear" produces better results)
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); //The minifying function
   
   glTexImage2D(GL_TEXTURE_2D, 0, 3, TGA_texture.imageWidth, TGA_texture.imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, TGA_texture.image);

   gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TGA_texture.imageWidth, TGA_texture.imageHeight, GL_RGB, GL_UNSIGNED_BYTE, TGA_texture.image);
   texture_ID=TGA_texture.ID;
   TGA_texture.FreeImage();
   glBindTexture(GL_TEXTURE_2D, 0);
   return 1; };

void C3DModel::clearTexture(void)
{
	texture_ID=0;
}

void C3DModel::clearMesh(void)
{
	cout << "Num obj: " << num_obj << endl;
   for(int i=0;i<num_obj;i++)
   {
      delete [] mesh_objs[i].vertex;
      delete [] mesh_objs[i].polygons;
      delete [] mesh_objs[i].texCoord;
   }
   //delete [] mesh_objs;
   num_obj=0;
}

void C3DModel::genList(void) {
   list=glGenLists(1);
   glNewList(list, GL_COMPILE);
   
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 
   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
   if(TGA_texture.ID>0)
      glBindTexture(GL_TEXTURE_2D, texture_ID);
   for(int j=0; j<num_obj;j++)
      {
      for(int i=0; i<mesh_objs[j].num_polygons; i++)
      {
         glColor3f(mesh_objs[j].diffuse_color[0],mesh_objs[j].diffuse_color[1],mesh_objs[j].diffuse_color[2]);     
         glBegin(GL_TRIANGLES);
         //if(TGA_texture.ID>0)
            glTexCoord2f(mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].a].u,mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].a].v);
         glVertex3f(mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].a].x+position.x, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].a].y+position.y, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].a].z+position.z);
         //if(TGA_texture.ID>0)
            glTexCoord2f(mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].b].u,mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].b].v);
         glVertex3f(mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].b].x+position.x, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].b].y+position.y, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].b].z+position.z);
         //if(TGA_texture.ID>0)
            glTexCoord2f(mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].c].u,mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].c].v);
         glVertex3f(mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].c].x+position.x, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].c].y+position.y, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].c].z+position.z);
         glEnd();
         }
      }
   glEndList();
   glBindTexture(GL_TEXTURE_2D, 0);
   };

void C3DModel::drawMesh(void) {
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 
   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
   if(TGA_texture.ID>=0)
      glBindTexture(GL_TEXTURE_2D, texture_ID);
   for(int j=0; j<num_obj;j++)
      {
		 glColor3f(mesh_objs[j].diffuse_color[0],mesh_objs[j].diffuse_color[1],mesh_objs[j].diffuse_color[2]); 
      for(int i=0; i<mesh_objs[j].num_polygons; i++)
      {
         glBegin(GL_TRIANGLES);
         //if(TGA_texture.ID>0)
            glTexCoord2f(mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].a].u,mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].a].v);
         glVertex3f(mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].a].x+position.x, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].a].y+position.y, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].a].z+position.z);
         //if(TGA_texture.ID>0)
            glTexCoord2f(mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].b].u,mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].b].v);
         glVertex3f(mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].b].x+position.x, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].b].y+position.y, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].b].z+position.z);
         //if(TGA_texture.ID>0)
            glTexCoord2f(mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].c].u,mesh_objs[j].texCoord[(int)mesh_objs[j].polygons[i].c].v);
         glVertex3f(mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].c].x+position.x, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].c].y+position.y, mesh_objs[j].vertex[(int)mesh_objs[j].polygons[i].c].z+position.z);
         glEnd();
         }
      }
   glBindTexture(GL_TEXTURE_2D, 0);
};

// also calculates center point (position)
void C3DModel::calculateBBox(void) {
   boundingBox.min.x=0;   boundingBox.max.x=0;
   boundingBox.min.y=0;   boundingBox.max.y=0;
   boundingBox.min.z=0;   boundingBox.max.z=0;

   for(unsigned int h=0; h<num_obj;h++)
   {
      for(unsigned int i=0; i<mesh_objs[h].num_vertices; i++)
      {
         if(mesh_objs[h].vertex[i].x<boundingBox.min.x) boundingBox.min.x = mesh_objs[h].vertex[i].x;
         if(mesh_objs[h].vertex[i].x>boundingBox.max.x) boundingBox.max.x = mesh_objs[h].vertex[i].x;
         if(mesh_objs[h].vertex[i].y<boundingBox.min.y) boundingBox.min.y = mesh_objs[h].vertex[i].y;
         if(mesh_objs[h].vertex[i].y>boundingBox.max.y) boundingBox.max.y = mesh_objs[h].vertex[i].y;
         if(mesh_objs[h].vertex[i].z<boundingBox.min.z) boundingBox.min.z = mesh_objs[h].vertex[i].z;
         if(mesh_objs[h].vertex[i].z>boundingBox.max.z) boundingBox.max.z = mesh_objs[h].vertex[i].z;
      }
   }
   boundingBox.min.x+=position.x;   boundingBox.max.x+=position.x;
   boundingBox.min.y+=position.y;   boundingBox.max.y+=position.y;
   boundingBox.min.z+=position.z;   boundingBox.max.z+=position.z;
         
   position.x = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x)/2;
   position.y = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y)/2;
   position.z = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z)/2;
   return;};

void C3DModel::drawBBox(void) {
   if(boundingBox.min.x=='\0')
      calculateBBox();
  
   glColor3f(1.0,1.0,1.0);
   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
   glBegin(GL_QUADS);
   
   glVertex3f(boundingBox.min.x, boundingBox.min.y, boundingBox.min.z);
   glVertex3f(boundingBox.min.x, boundingBox.max.y, boundingBox.min.z);
   glVertex3f(boundingBox.max.x, boundingBox.max.y, boundingBox.min.z);
   glVertex3f(boundingBox.max.x, boundingBox.min.y, boundingBox.min.z);
   
   glVertex3f(boundingBox.min.x, boundingBox.min.y, boundingBox.max.z);
   glVertex3f(boundingBox.min.x, boundingBox.max.y, boundingBox.max.z);
   glVertex3f(boundingBox.max.x, boundingBox.max.y, boundingBox.max.z);
   glVertex3f(boundingBox.max.x, boundingBox.min.y, boundingBox.max.z);
   
   glVertex3f(boundingBox.min.x, boundingBox.min.y, boundingBox.max.z);
   glVertex3f(boundingBox.min.x, boundingBox.max.y, boundingBox.max.z);
   glVertex3f(boundingBox.min.x, boundingBox.max.y, boundingBox.min.z);
   glVertex3f(boundingBox.min.x, boundingBox.min.y, boundingBox.min.z);
   
   glVertex3f(boundingBox.max.x, boundingBox.min.y, boundingBox.max.z);
   glVertex3f(boundingBox.max.x, boundingBox.max.y, boundingBox.max.z);
   glVertex3f(boundingBox.max.x, boundingBox.max.y, boundingBox.min.z);
   glVertex3f(boundingBox.max.x, boundingBox.min.y, boundingBox.min.z);
   
   glVertex3f(boundingBox.min.x, boundingBox.max.y, boundingBox.max.z);
   glVertex3f(boundingBox.min.x, boundingBox.max.y, boundingBox.min.z);
   glVertex3f(boundingBox.max.x, boundingBox.max.y, boundingBox.min.z);
   glVertex3f(boundingBox.max.x, boundingBox.max.y, boundingBox.max.z);
   
   glVertex3f(boundingBox.min.x, boundingBox.min.y, boundingBox.max.z);
   glVertex3f(boundingBox.min.x, boundingBox.min.y, boundingBox.min.z);
   glVertex3f(boundingBox.max.x, boundingBox.min.y, boundingBox.min.z);
   glVertex3f(boundingBox.max.x, boundingBox.min.y, boundingBox.max.z);
   glEnd();
   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
   return; };
   
void C3DModel::calculateBSphere(void)
{
   if(boundingBox.min.x=='\0')
      calculateBBox();
   double radius=0;
   if(boundingBox.max.x-boundingBox.min.x>radius)
      radius=boundingBox.max.x-boundingBox.min.x;
   if(boundingBox.max.y-boundingBox.min.y>radius)
      radius=boundingBox.max.y-boundingBox.min.y;
   if(boundingBox.max.z-boundingBox.min.z>radius)
      radius=boundingBox.max.z-boundingBox.min.z;
   boundingSphere.radius=radius/2;
   boundingSphere.position=position;
};   

void C3DModel::drawBSphere(void)
{
   if(boundingSphere.radius=='\0')
      calculateBSphere();   
   glutWireSphere(boundingSphere.radius,12,12);
};


void C3DModel::changePosition(double x, double y, double z) {
   position.x+=x;
   position.y+=y;
   position.z+=z; 
};

void C3DModel::changeAngle(double x, double y, double z)
{
   x_ang+=x; 
   y_ang+=y;
   if(x_ang>360)
      x_ang-=360;
   if(x_ang<0)
      x_ang+=360;
   if(y_ang>360)
      y_ang-=360;
   if(y_ang<0)
      y_ang+=360;
   if(z_ang>360)
      z_ang-=360;
   if(z_ang<0)
      z_ang+=360;
};

C3DModel & C3DModel::operator=(const C3DModel & model)
{
	num_obj=model.num_obj;
	mesh_objs = new MESH_Object[num_obj];
	for(int i=0; i<num_obj; i++)
	{
		strcpy(mesh_objs[i].name,model.mesh_objs[i].name);
		mesh_objs[i].num_vertices = model.mesh_objs[i].num_vertices;
		mesh_objs[i].num_polygons = model.mesh_objs[i].num_polygons;
		mesh_objs[i].num_texCoord = model.mesh_objs[i].num_texCoord;
		mesh_objs[i].vertex = new CVector3[mesh_objs[i].num_vertices];
		for(int h=0; h<mesh_objs[i].num_vertices; h++)
			mesh_objs[i].vertex[h] = model.mesh_objs[i].vertex[h];
		mesh_objs[i].polygons = new POLY_type[mesh_objs[i].num_polygons];
		for(int h=0; h<mesh_objs[i].num_polygons; h++)
			mesh_objs[i].polygons[h] = model.mesh_objs[i].polygons[h];
		mesh_objs[i].texCoord = new UVCoord2[mesh_objs[i].num_texCoord];
		for(int h=0; h<mesh_objs[i].num_texCoord; h++)
			mesh_objs[i].texCoord[h] = model.mesh_objs[i].texCoord[h];
		for(int h=0; h<3; h++)
			mesh_objs[i].diffuse_color[h]=model.mesh_objs[i].diffuse_color[h];
	}
	TGA_texture = model.TGA_texture;
	list = model.list;
	position = model.position;
	x_ang=model.x_ang;
	y_ang=model.y_ang;
	z_ang=model.z_ang;
	boundingBox=model.boundingBox;
	boundingSphere=model.boundingSphere;
	texture_ID = model.texture_ID;
	return *this;
}

C3DModel::~C3DModel() {
   //glDeleteTextures(1,&TGA_texture.ID); //temp fix
   clearMesh();
};
